using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace testApp
{
    public partial class Win32 : IDisposable
    {
        [DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)]
        static extern IntPtr CreateFile
        (
            string FileName,          // file name
            uint DesiredAccess,       // access mode
            uint ShareMode,           // share mode
            uint SecurityAttributes,  // Security Attributes
            uint CreationDisposition, // how to create
            uint FlagsAndAttributes,  // file attributes
            int hTemplateFile         // handle to template file
        );

        [DllImport("kernel32", SetLastError = true)]
        static extern bool CloseHandle
        (
            IntPtr hObject // handle to object
        );

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern int DeviceIoControl
        (
            IntPtr handle,
            uint dwIoControlCode,
            byte[] lpInBuffer,
            int nInBufferSize,
            byte[] lpOutBuffer,
            int nOutBufferSize,
            out int lpBytesReturned,
            IntPtr lpOverlapped
        );

        [DllImport("setupapi.dll", CharSet = CharSet.Auto)]
        static extern IntPtr SetupDiGetClassDevs
        (
            ref Guid ClassGuid,
            IntPtr Enumerator,
            IntPtr hwndParent,
            UInt32 Flags
        );

        [DllImport(@"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern Boolean SetupDiEnumDeviceInterfaces
        (
           IntPtr hDevInfo,
           IntPtr devInfo,
           ref Guid interfaceClassGuid,
           UInt32 memberIndex,
           ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData
        );

        [DllImport(@"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern Boolean SetupDiGetDeviceInterfaceDetail(
           IntPtr hDevInfo,
           ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData,
           ref SP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData,
           UInt32 deviceInterfaceDetailDataSize,
           out UInt32 requiredSize,
           ref SP_DEVINFO_DATA deviceInfoData
        );

        [DllImport(@"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern bool SetupDiDestroyDeviceInfoList(IntPtr hDevInfo);

        private SP_DEVICE_INTERFACE_DATA dia;
        private SP_DEVINFO_DATA da;
        private SP_DEVICE_INTERFACE_DETAIL_DATA didd;

        private IntPtr hR1000 = IntPtr.Zero;

        private bool disposed = false;

        public Win32()
        {
            hR1000 = IntPtr.Zero;
            disposed = false;
        }

        static byte[] StructureToByteArray(object obj)
        {

            int len = Marshal.SizeOf(obj);

            byte[] arr = new byte[len];

            IntPtr ptr = Marshal.AllocHGlobal(len);

            Marshal.StructureToPtr(obj, ptr, true);

            Marshal.Copy(ptr, arr, 0, len);

            Marshal.FreeHGlobal(ptr);

            return arr;

        }

        static void ByteArrayToStructure(byte[] bytearray, ref object obj)
        {
            int len = Marshal.SizeOf(obj);

            IntPtr i = Marshal.AllocHGlobal(len);

            Marshal.Copy(bytearray, 0, i, len);

            obj = Marshal.PtrToStructure(i, obj.GetType());

            Marshal.FreeHGlobal(i);
        }

        public bool DriverOpen()
        {
            IntPtr h = SetupDiGetClassDevs(ref R1000GUID, IntPtr.Zero, IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
            if (h.ToInt32() == INVALID_HANDLE_VALUE)
            {
                return false;
            }

            // create a Device Interface Data structure
            dia = new SP_DEVICE_INTERFACE_DATA();
            dia.cbSize = (uint)Marshal.SizeOf(dia);

            if (!SetupDiEnumDeviceInterfaces(h, IntPtr.Zero, ref R1000GUID, 0, ref dia))
            {
                SetupDiDestroyDeviceInfoList(h);
                return false;
            }

            // build a DevInfo Data structure
            da = new SP_DEVINFO_DATA();
            da.cbSize = (uint)Marshal.SizeOf(da);

            // build a Device Interface Detail Data structure
            didd = new SP_DEVICE_INTERFACE_DETAIL_DATA();
            if (IntPtr.Size == 8) // for 64 bit operating systems
            {
                didd.cbSize = 8;
            }
            else
            {
                didd.cbSize = (uint)(4 + Marshal.SystemDefaultCharSize); // for 32 bit systems
            }

            // now we can get some more detailed information
            uint nRequiredSize = 0;
            uint nBytes = 1024;

            if (!SetupDiGetDeviceInterfaceDetail(h, ref dia, ref didd, nBytes, out nRequiredSize, ref da))
            {
                SetupDiDestroyDeviceInfoList(h);
                return false;
            }

            SetupDiDestroyDeviceInfoList(h);

            hR1000 = CreateFile(didd.DevicePath,
                                    GENERIC_READ | GENERIC_WRITE,
                                    FILE_SHARE_READ | FILE_SHARE_WRITE,
                                    0,
                                    OPEN_EXISTING,
                                    FILE_FLAG_OVERLAPPED,
                                    0);
            if (hR1000.ToInt32() == INVALID_HANDLE_VALUE)
            {
                return false;
            }

            return true;
        }

        public void DriverClose()
        {
            if (hR1000 != IntPtr.Zero)
            {
                CloseHandle(hR1000);
                hR1000 = IntPtr.Zero;
            }
        }

        public bool writeReg(ushort addr, uint data)
        {
            R1000_REG_REQ req = new R1000_REG_REQ(HOST_REG_REQ_ACCESS_WRITE, addr, data);

            byte[] reqBuf = StructureToByteArray(req);
            if (!write(reqBuf, reqBuf.Length))
            {
                data = 0;
                return false;
            }

            return true;
        }

        public bool readReg(ushort addr, out uint data)
        {
            int bytesReturned, remaining;

            R1000_REG_REQ req = new R1000_REG_REQ(HOST_REG_REQ_ACCESS_READ, addr, 0);

            byte[] reqBuf = StructureToByteArray(req);
            if (!write(reqBuf, reqBuf.Length))
            {
                data = 0;
                return false;
            }

            R1000_REG_RESP resp = new R1000_REG_RESP();
            byte[] respBuf = new byte[Marshal.SizeOf(resp)];
            if (!read(respBuf, respBuf.Length, out bytesReturned, out remaining))
            {
                data = 0;
                return false;
            }

            object tmpObject = (object)resp;
            ByteArrayToStructure(respBuf, ref tmpObject);
            resp = (R1000_REG_RESP)tmpObject;

            if (resp.regAddr != addr)
            {
                data = 0;
                return false;
            }

            data = resp.regData;
            return true;
        }

        private bool write(byte[] buf, int bufSize)
        {
            int bytesReturned;

            if (DeviceIoControl(hR1000,
                               IOCTL_HARVEMAC_WRITE,
                               buf,
                               bufSize,
                               null,
                               0,
                               out bytesReturned,
                               IntPtr.Zero) == 0)
            {
                return false;
            }

            return true;
        }

        private bool read(byte[] buf, int bufSize, out int BytesReturned, out int remaining)
        {
            byte[] remainingBytes = new byte[4];

            if (buf != null)
            {
                if (DeviceIoControl(hR1000,
                                    IOCTL_HARVEMAC_READ,
                                    null,
                                    0,
                                    buf,
                                    bufSize,
                                    out BytesReturned,
                                    IntPtr.Zero) == 0)
                {
                    remaining = 0;
                    return false;
                }
            }
            else
            {
                if (DeviceIoControl(hR1000,
                                    IOCTL_HARVEMAC_READ_CNT,
                                    null,
                                    0,
                                    remainingBytes,
                                    remainingBytes.Length,
                                    out BytesReturned,
                                    IntPtr.Zero) == 0)
                {
                    remaining = 0;
                    return false;
                }
            }

            remaining = BitConverter.ToInt32(remainingBytes, 0);
            return true;
        }


        // Public IDisposable.Dispose implementation - calls the internal helper,
        public void Dispose()
        {
            Dispose(true);
        }

        // This is "friendly" wrapper method for IDisposable.Dispose
        public void Close()
        {
            // Call the internal diposal helper
            Dispose(true);
        }

        private void Dispose(bool disposing)
        {
            if (!disposed && disposing)
            {
                DriverClose();

                // Keep us from calling resetting or closing multiple times
                disposed = true;
            }
        }
    }
}